/** A is a where gameplay occurs.
 *@param target The buffer to render the map to.
 *@param x The x offset into buffer to render the map.
 *@param y The y offset into buffer to render the map.
 *@param width The width of the map.
 *@param height The height of the map.
 *@param level The level of the map, starting at 1.
 */
function Map(target, x, y, width, height, level)
{
	var i;
	
	this.buffer = null;
	this.target = target;
	this.xOfs = x;
	this.yOfs = y;
	this.width = width;
	this.height = height;
	this.level = level;
	this._tiles = [];
	this._ents = [];
	this._blockWalkMap = [];
	this._blockVisMap = [];
	this._entCountMap = [];
	this._visSet = [];
	this._lastVisSet = [];
	this._knownMap = [];
	this._defaultX = 0;
	this._defaultY = 10;

	// Create all the tiles and vis set data points
	this._tiles = [];
	for(i = 0; i < this.width * this.height; ++i)
	{
		this._tiles.push(0);
		this._visSet.push(0);
		this._lastVisSet.push(0);
		this._knownMap.push(0);
	}
	
	// Construct the rendering surface.
	this.buffer = new ScreenBuffer(this.width, this.height);
}
Map.prototype =
{
	/** Get the serialized form.
	 */
	toRON: function()
	{
		var i;

		var ret =
		{
			width: this.width,
			height: this.height,
			level: this.level,
			buffer: this.buffer,
			_ents: [],
			_tiles: this._tiles,
			visRle: []
		};

		// Build the entity list ommitting the player's character.
		for(i = 0; i < this._ents.length; ++i)
		{
			if(this._ents[i] == dhack.player)
				continue;
			ret._ents.push(this._ents[i]);
		}

		// RLE-compress visibility information
		var lastValue = 0;
		var count = 0;
		for(i = 0; i < this._knownMap.length; ++i)
		{
			if(this._knownMap[i] == lastValue)
				++count;
			else
			{
				ret.visRle.push(count);
				lastValue = this._knownMap[i];
				count = 1;
			}
		}
		ret.visRle.push(count);
		return ret;
	},
	/** Called after deserialization.
	 */
	fromRON: function()
	{
		// Load visibility information.
		var i, j, visible = false, ofs = 0;
		for(i = 0; i < this.visRle.length; ++i)
		{
			for(j = 0; j < this.visRle[i]; ++j)
			{
				this._knownMap[ofs++] = visible ? 1 : 0;
			}
			visible = !visible;
		}
		/*[targets=html]*/
		delete(this.visRle);
		/*[/targets]*/
		this._onAfterLoad();
	},
	/** Generate the features of the map including mobiles and objects.
	*/
	generate: function()
	{
		var i;

		// Add a wall
		for(i = 0; i < 15; ++i)
			this._tiles[(5 + i) * this.width + 10] = 1;
		
		// Add a door
		this._tiles[13 * this.width + 10] = 0;
		var door = new DoorEntity();
		door.x = 10;
		door.y = 13;
		this._ents.push(door);
		
		// Add some items
		for(i = 0; i < 20; ++i)
		{
			//var rat = Item.newById((i % 2 == 0) ? "dagger" : "club");
			var rat = Mobile.newById((i % 2 == 0) ? "rat" : "giant rat");
			rat.x = rand.next(0, this.width + 1);
			rat.y = rand.next(0, this.height + 1);
			this._ents.push(rat);
		}
		
		// These two method calls are the secret sauce to level generation.
		this._onAfterGenerate();
		this._onAfterLoad();
	},
	/** Loads a map from a static level definition.
	 *@param def The level definition.
	 */
	fromStatic: function(def)
	{
		// Validations
		if(def.width > this.width ||
			def.height > this.height)
			throw new Error("Static map too large for map area");
		if(def.map.length != def.width * def.height)
			throw new Error("Map string not the right length");
		
		// Process map locations
		var srcOfs = 0;
		var e, destOfs, c, x, y;
		for(y = 0; y < def.height; ++y)
		{
			destOfs = y * this.width;
			for(x = 0; x < def.width; ++x)
			{
				c = def.map.charAt(srcOfs++);
				this._tiles[destOfs] = 0;
				if(c in def.legend)
				{
					var parts = def.legend[c].split(".");
					switch(parts[0])
					{
						case "info":
							switch(parts[1])
							{
								case "player":
									this._defaultX = x;
									this._defaultY = y;
									break;
								case "door":
									var e = new DoorEntity();
									e.x = x;
									e.y = y;
									this._ents.push(e);
									break;
								default:
									throw new Error("Invalid info entity \"" + parts[1] + "\"");
							}
							break;
						case "mobile":
							e = Mobile.newById(parts[1]);
							e.x = x;
							e.y = y;
							this._ents.push(e);
							break;
						case "item":
							e = Item.newById(parts[1]);
							e.x = x;
							e.y = y;
							this._ents.push(e);
							break;
						case "tile":
							this._tiles[destOfs] = tilesByName[parts[1]];
							break;
						default:
							throw new Error("Invalid entity type \"" + parts[0] + "\"");
					}
				}
				else
					throw Error("Invalid character in static map: \"" + c + "\"");
				++destOfs;
			}
		}
		
		// These two method calls are the secret sauce to level generation.
		this._onAfterGenerate();
		this._onAfterLoad();		
	},
	/** Set entity parents and do initial arrival triggers.
	 */
	_onAfterGenerate: function()
	{
		for(i = 0; i < this._ents.length; ++i)
		{
			var ent = this._ents[i];
			ent.parent = this;
			ent.onArriveAtPosition(true);
		}
	},
	/** Build the base flag maps.
	 */
	_onAfterLoad: function()
	{
		for(i = 0; i < this.width * this.height; ++i)
		{
			var tile = Tiles[this._tiles[i]];
			this._blockWalkMap.push(tile.blockWalk ? 1 : 0);
			this._blockVisMap.push(tile.blockVis ? 1 : 0);
			this._entCountMap.push(0);
		}
		for(i = 0; i < this._ents.length; ++i)
		{
			var ent = this._ents[i];
			if(ent.x < 0 ||
				ent.y < 0 ||
				ent.x >= this.width ||
				ent.y >= this.height)
				continue;
			var idx = ent.y * this.width + ent.x;
			++this._entCountMap[idx];
			if(ent.blockWalk)
				++this._blockWalkMap[idx];
			if(ent.blockVis)
				++this._blockVisMap[idx];
		}
	},
	/** Called to do a turn for all entities in the map.
	*
	* This will continue executing turns for entities on the map until the
	* player's turn comes around.
	*/
	doTurn: function()
	{
		function timeSort(a, b)
		{
			return b.time - a.time;
		}
		
		// Waiting for player turn loop
		for(;;)
		{
			var i, ent, workingList, updateList;
			
			// Build the list of updateable mobiles
			updateList = [];
			for(i = 0; i < this._ents.length; ++i)
			{
				ent = this._ents[i];
				if(!ent.isMobile)
					continue
				if(ent.time > 0)
					updateList.push(ent);
			}
			updateList.sort(timeSort);
			
			// Waiting for all mobiles to be done with their turns
			while(updateList.length > 0)
			{
				workingList = updateList;
				
				// Do turns
				for(i = 0; i < workingList.length; ++i)
				{
					ent = workingList[i];
					// If the player is ready to have a turn we bail
					if(ent === dhack.player)
					{
						this._updateVisibilitySetFrom(dhack.player);
						this._applyVisibilitySet();
						dhack.onTurnEnded();
						return;
					}
					ent.doTurn();
				}
				
				// Update the list
				updateList = [];
				for(i = 0; i < workingList.length; ++i)
				{
					ent = workingList[i];
					if(ent.time > 0)
						updateList.push(ent);
				}
				updateList.sort(timeSort);
			}

			// Add time to mobiles
			for(i = 0; i < this._ents.length; ++i)
			{
				ent = this._ents[i];
				if(!ent.isMobile)
					continue
				ent.time += ent.getEffectiveSpeed();
			}
			updateList.sort(timeSort);
		}
	},
	/** Adds the player's entity to the map.
	 *@param player The player's entity.
	 *@param isLoad Is this the result of a game load? If so we won't mess with the player's position.
	 */
	addPlayer: function(player, isLoad)
	{
		if(!isLoad)
		{
			player.x = this._defaultX;
			player.y = this._defaultY;
		}
		this._player = player;
		this.addEntity(player);
	},
	/** Add an entity to the map.
	@param ent Entity to add.
	*/
	addEntity: function(ent)
	{
		this._ents.push(ent);
		ent.parent = this;
		this._onArrive(ent);
		this.renderPosition(ent.x, ent.y);
	},
	/** Remove an entity from the map.
	@param ent Entity to remove.
	*/
	removeEntity: function(ent)
	{
		this._onLeave(ent);
		ent.parent = null;
		var idx = this._ents.indexOf(ent);
		if(idx < 0)
			return;
		this._ents.splice(idx, 1);
		this.renderPosition(ent.x, ent.y);
	},
	/** Try to move an entity.
	@param ent Entity to move.
	@param x New X location.
	@param y New Y location.
	@returns true if successful, false otherwise.
	*/
	tryMoveEntity: function(ent, x, y)
	{
		if(x < 0 ||
			y < 0 ||
			x >= this.width ||
			y >= this.height)
			return false;
		if(this._blockWalkMap[y * this.width + x] > 0)
			return false;
		
		// Prevent moving if encumbered
		if(ent.flags.encumbered)
		{
			if(ent === dhack.player)
				dhack.logWindow.tempMessage("%90You are carrying too much and cannot move.%f0");
			return false;
		}

		this.removeEntity(ent);
		var lastX = ent.x, lastY = ent.y;
		ent.x = x;
		ent.y = y;
		this.addEntity(ent);
		this.renderPosition(lastX, lastY);
		return true;
	},
	/** Return the tile at a position or null if out of bounds.
	@param x X position.
	@param y Y position.
	 */
	getTile: function(x, y)
	{
		if(x < 0 ||
			y < 0 ||
			x >= this.width ||
			y >= this.height)
			return null;
		return Tiles[this._tiles[y * this.width + x]];
	},
	/** Return an array of all of the entities at a position with the first
	 * entity returned as the top entity at that location.
	 *@param x X parameter.
	 *@param y Y parameter.
	 */
	getEntitiesAtPosition: function(x, y)
	{
		var ret = [], i, ent;
		if(x < 0 ||
			y < 0 ||
			x >= this.width ||
			y >= this.height)
			return ret;
		if(this._entCountMap[y * this.width + x] > 0)
		{
			for(i = this._ents.length - 1; i >= 0; --i)
			{
				ent = this._ents[i];
				if(ent.x != x ||
					ent.y != y)
					continue;
				ret.push(ent);
			}
		}
		return ret;
	},
	/** Returns a description of the position.
	@param x X position.
	@param y Y position.
	*/
	getPositionDescription: function(x, y)
	{
		if(x < 0 ||
			y < 0 ||
			x >= this.width ||
			y >= this.height)
			return "Unknown";

		var i = y * this.width + x;
		if(this._knownMap[i] == 0)
			return "Unknown";
		var desc = Tiles[this._tiles[i]].name;
		if(this._visSet[i] == 0)
			return desc;
		if(this._entCountMap[i] > 0)
		{
			var foundCount = 0;
			for(i = this._ents.length - 1; i >= 0; --i)
			{
				var ent = this._ents[i];
				if(ent.x != x ||
					ent.y != y)
					continue;
				if(foundCount == 0)
					desc += ": ";
				else
					desc += ", ";
				++foundCount;
				desc += ent.name;
			}
		}
		return desc;
	},
	/** Called when an entity arrives at a position.
	@param ent The entity.
	*/
	_onArrive: function(ent)
	{
		if(ent.x < 0 ||
			ent.y < 0 ||
			ent.x >= this.width ||
			ent.y >= this.height)
			return;
		var i = ent.y * this.width + ent.x;
		if(ent.blockWalk)
			++this._blockWalkMap[i];
		if(ent.blockVis)
			++this._blockVisMap[i];
		++this._entCountMap[i];
		ent.onArriveAtPosition(false);
		if(ent == dhack.player)
		{
			this._updateVisibilitySetFrom(ent);
			this._applyVisibilitySet();
		}
	},
	/** Called when an entity leaves a position.
	@param ent The entity.
	*/
	_onLeave: function(ent)
	{
		if(ent.x < 0 ||
			ent.y < 0 ||
			ent.x >= this.width ||
			ent.y >= this.height)
			return;
		var i = ent.y * this.width + ent.x;
		if(ent.blockWalk)
			--this._blockWalkMap[i];
		if(ent.blockVis)
			--this._blockVisMap[i];
		--this._entCountMap[i];
		ent.onLeavePosition();
	},
	/** Determines if point b is visible from point a.
	 *@param ax X position of point a.
	 *@param ay Y position of point a.
	 *@param bx X position of point b.
	 *@param by Y position of point b.
	 *@param range Optional range for the test.
	 */
	isVisibleFrom: function(ax, ay, bx, by, range)
	{
		var dx = ax - bx;
		var dy = ay - by;
		if(range &&
			dx * dx + dy * dy > range * range)
			return false;
		var absdx = dx;
		var absdy = dy;
		if(absdx < 0) absdx *= -1;
		if(absdy < 0) absdy *= -1;

		// Line driver, based on Bresenham's line algorithm
		// If the line is steeper that 45% reflect the line
		var steep = absdy > absdx;
		var x0, x1, y0, y1;
		if(steep)
		{
			x0 = ay;
			x1 = by;
			y0 = ax;
			y1 = bx;
		}
		else
		{
			x0 = ax;
			x1 = bx;
			y0 = ay;
			y1 = by;
		}
		dx = x1 - x0;
		dy = y1 - y0;
		absdx = dx;
		absdy = dy;
		if(absdx < 0) absdx *= -1;
		if(absdy < 0) absdy *= -1;
		var error = absdx / 2;
		var stepy = 1;
		if(y0 > y1) stepy = -1;
		var stepx = 1;
		if(x0 > x1) stepx = -1;
		var realx, realy;
		var liney = y0;
		var linex = x0;
		var steps = x1 - x0;
		if(steps < 0) steps *= -1;
		// NOTE: This for loop omitts the first and last positions
		for(var iStep = 1; iStep < steps; ++iStep)
		{
			error -= absdy;
			if(error < 0)
			{
				liney += stepy;
				error += absdx;
			}
			linex += stepx;
			if(steep)
			{
				realx = liney;
				realy = linex;
			}
			else
			{
				realx = linex;
				realy = liney;
			}

			// Out of bounds tests always return false.
			if(realx < 0 ||
				realy < 0 ||
				realx >= this.width ||
				realy >= this.height)
				return false;

			// At this point, realx and realy are our absolute map coordinates along this line.
			// If this map position blocks visibility we return false;
			if(this._blockVisMap[realy * this.width + realx] > 0)
				return false;
		}

		// If we get here without returning false we have a line of sight.
		return true;
	},
	/** Updates the visibility set data based on an entity.
	 *
	 * Visibility method that uses a line-of-sight test for every map position within the visible
	 * range. Positions that were marked as visible in previous line-of-sight tests will be skipped.
	 * A post-processing step is then applied to clean up the most common artifacts caused by this
	 * type of visibility method.
	 *
	 * This is a useful visibility method to use for very ranged-combat intensive games as any
	 * visibile mobile will nessecarily have line-of-sight to and from the player (in most cases).
	 *
	 * The data format for the visibility buffer is as follows:
	 * 0	Position is not visible
	 * 1	Position was marked visible as a result of the post-processing phase
	 * 2	Position was marked visible as the point of obstruction of a line of sight
	 * 4	Position was marked visible as it is within line of sight
	 *
	 * This is significant only to the post-processing phase, which uses sums of neighbor cell
	 * visibility to determine if a cell should be marked visible.
	 *
	 * Here's how the post processing works:
	 *
	 * We look at every position in our visibility range, plus a one-cell periffery. If a position's
	 * TILE blocks visibility AND it is not already marked visibile then we do this:
	 *
	 * Add up the visibility bytes from all eight adjacent cells who's tiles. If any one of these tiles
	 * has a value of 4 we mark our position as visible too. This gets the "looking down a wall"
	 * and "interior corner" edge cases which cause most of the artifacting with basic ray casting. This
	 * does create a little bit of bleeding though.
	 *
	 * This visibility method was ported from an unreleased C# project I did about a year prior and is
	 * not 100% optimized for JavaScript. As it is it is not suitable for large maps. The origional C#
	 * implementation used a helper class that allowed this.
	 *@param ent The entity to use.
	 */
	_updateVisibilitySetFrom: function(ent)
	{
		var i;

		// Swap vis sets
		for(i = 0; i < this._visSet.length; ++i)
		{
			this._lastVisSet[i] = this._visSet[i];
			this._visSet[i] = 0;
		}

		// Data setup
		var mobVisRangeSquared = (ent.visibilityRange - 0.5) * (ent.visibilityRange - 0.5);
		var mx = ent.x;
		var my = ent.y;

		// Spiral driver
		var spMinX = mx - ent.visibilityRange;
		var spMinY = my - ent.visibilityRange;
		var spMaxX = mx + ent.visibilityRange;
		var spMaxY = my + ent.visibilityRange;
		var ix = spMinX;
		var iy = spMinY;
		var spStepX = 1;
		var spStepY = 0;
		while(true)
		{
			// End of spiral, break
			if(spMinX == spMaxX ||
				spMinY == spMaxY)
				  break;

			// Right extent
			if(spStepX == 1 &&
				ix >= spMaxX - 1)
			{
				spStepX = 0;
				spStepY = 1;
				spMinY++;
			}
			// Left extent
			if(spStepX == -1 &&
				ix <= spMinX)
			{
				spStepX = 0;
				spStepY = -1;
				spMaxY--;
			}
			// Lower extent
			if(spStepY == 1 &&
				iy >= spMaxY - 1)
			{
				spStepY = 0;
				spStepX = -1;
				spMaxX--;
			}
			// Upper extent
			if(spStepY == -1 &&
				iy <= spMinY)
			{
				spStepY = 0;
				spStepX = 1;
				spMinX++;
			}

			// Process position. We do this in a do ... while(false) so we can use break
			// to fall through to the next spiral loop with break.
			do
			{
				// Out of bounds short-circut
				if(ix < 0 ||
					iy < 0 ||
					ix >= this.width ||
					iy >= this.height)
					break;

				// Already set to visible short-circut
				if(this._visSet[iy * this.width + ix] > 0)
					break;

				// Out-of-Range Short Circut
				var dx = ix - mx;
				var dy = iy - my;
				if(dx * dx + dy * dy > mobVisRangeSquared)
					break;
				var absdx = dx;
				var absdy = dy;
				if(absdx < 0) absdx *= -1;
				if(absdy < 0) absdy *= -1;

				// Line driver, based on Bresenham's line algorithm
				// If the line is steeper that 45% reflect the line
				var steep = absdy > absdx;
				var x0, x1, y0, y1;
				if(steep)
				{
					x0 = my;
					x1 = iy;
					y0 = mx;
					y1 = ix;
				}
				else
				{
					x0 = mx;
					x1 = ix;
					y0 = my;
					y1 = iy;
				}
				dx = x1 - x0;
				dy = y1 - y0;
				absdx = dx;
				absdy = dy;
				if(absdx < 0) absdx *= -1;
				if(absdy < 0) absdy *= -1;
				var error = absdx / 2;
				var stepy = 1;
				if(y0 > y1) stepy = -1;
				var stepx = 1;
				if(x0 > x1) stepx = -1;
				var realx, realy;
				var liney = y0;
				var linex = x0;
				var steps = x1 - x0;
				if(steps < 0) steps *= -1;
				for(var iStep = 0; iStep <= steps; ++iStep)
				{
					if(steep)
					{
						realx = liney;
						realy = linex;
					}
					else
					{
						realx = linex;
						realy = liney;
					}
					error -= absdy;
					if(error < 0)
					{
						liney += stepy;
						error += absdx;
					}

					// At this point, realx and realy are our absolute map coordinates along this line.
					// If this map position blocks visibility we set it to blocking visible and bail
					// out of the line driver.
					if(realx >= 0 &&
						realy >= 0 &&
						realx < this.width &&
						realy < this.height)
					{
						if(this._blockVisMap[realy * this.width + realx] > 0)
						{
							this._visSet[realy * this.width + realx] = 2;
							break;
						}
						// Otherwise we have a map position that is visible from the reference mobile.
						this._visSet[realy * this.width + realx] = 4;
					}
					linex += stepx;
				}
			} while(false);

			// Step position
			ix += spStepX;
			iy += spStepY;
		}

		// Postprocessing, see method remarks for logic
		spMinX = mx - ent.visibilityRange;
		spMinY = my - ent.visibilityRange;
		spMaxX = mx + ent.visibilityRange;
		spMaxY = my + ent.visibilityRange;
		for(iy = spMinY - 1; iy < spMaxY + 1; ++iy)
		{
			for(ix = spMinX - 1; ix < spMaxX + 1; ++ix)
			{
				// Out of bounds short-circut
				if(ix < 0 ||
					iy < 0 ||
					ix >= this.width ||
					iy >= this.height)
					break;

				var tile = Tiles[this._tiles[iy * this.width + ix]];
				if(!tile.blockVis ||
					this._visSet[iy * this.width + ix] > 0)
					continue;

				// Inspect West
				if(ix > 0)
				{
					// W
					if(this._visSet[(iy) * this.width + (ix - 1)] >= 4)
					{
						this._visSet[(iy) * this.width + (ix)] = 1;
						continue;
					}
					// NW
					if(iy > 0 &&
						this._visSet[(iy - 1) * this.width + (ix - 1)] >= 4)
					{
						this._visSet[(iy) * this.width + (ix)] = 1;
						continue;
					}
					// SW
					if(iy < this.height - 1 &&
						this._visSet[(iy + 1) * this.width + (ix - 1)] >= 4)
					{
						this._visSet[(iy) * this.width + (ix)] = 1;
						continue;
					}
				}
				// Inspect East
				if(ix < this.width - 1)
				{
					// E
					if(this._visSet[(iy) * this.width + (ix + 1)] >= 4)
					{
						this._visSet[(iy) * this.width + (ix)] = 1;
						continue;
					}
					// NE
					if(iy > 0 &&
						this._visSet[(iy - 1) * this.width + (ix + 1)] >= 4)
					{
						this._visSet[(iy) * this.width + (ix)] = 1;
						continue;
					}
					// SE
					if(iy < this.height - 1 &&
						this._visSet[(iy + 1) * this.width + (ix + 1)] >= 4)
					{
						this._visSet[(iy) * this.width + (ix)] = 1;
						continue;
					}
				}
				// Inspect North
				if(iy > 0 &&
					this._visSet[(iy - 1) * this.width + (ix)] >= 4)
				{
					this._visSet[(iy) * this.width + (ix)] = 1;
					continue;
				}
				// Inspect South
				if(iy < this.height - 1 &&
					this._visSet[(iy + 1) * this.width + (ix)] >= 4)
				{
					this._visSet[(iy) * this.width + (ix)] = 1;
					continue;
				}
			}
		}
	},
	/** Analyses the last visibility set and the current visibility set and
	 *takes appropriote action such as marking tiles as known and redrawing
	 *positions that have changed.
	 */
	_applyVisibilitySet: function()
	{
		var ix, iy, ofs = 0;
		for(iy = 0; iy < this.height; ++iy)
		{
			for(ix = 0; ix < this.width; ++ix)
			{
				if(this._visSet[ofs] > 0)
					this._knownMap[ofs] = 1;
				if((this._lastVisSet[ofs] > 0) !=
					(this._visSet[ofs] > 0))
					this.renderPosition(ix, iy, true);
				++ofs;
			}
		}
	},
	/** Render a map location.
	 *@param x X location.
	 *@param y Y location.
	 *@param isApplyVisSet A special flag used by _applyVisibilitySet for special rendering of previously-known locations.
	 */
	renderPosition: function(x, y, isApplyVisSet)
	{
		if(!this.buffer ||
			!this.target ||
			x < 0 ||
			y < 0 ||
			x >= this.width ||
			y >= this.height)
			return;
		// Visibility check
		var ofs = y * this.width + x;
		var tile = Tiles[this._tiles[ofs]];
		if(this._visSet[ofs] > 0)
		{
			this.buffer.setCell(x + this.xOfs, y + this.yOfs, tile.character, tile.fg, tile.bg);
			if(this._entCountMap[ofs] > 0)
			{
				for(var i = 0; i < this._ents.length; ++i)
				{
					ent = this._ents[i];
					if(ent.x != x ||
						ent.y != y)
						continue;
					this.buffer.setCell(ent.x + this.xOfs, ent.y + this.yOfs, ent.character, ent.fg, ent.bg);
					// Kinda hacky, to prevent displaying stuff on top of the
					// player we bail if we just rendered them.
					if(ent === this._player)
						break;
				}
			}
			this.target.setRawCell(x + this.xOfs, y + this.yOfs, this.buffer.getCell(x + this.xOfs, y + this.yOfs));
		}
		else if(this._knownMap[ofs] > 0)
		{
			// This is a known location that is no longer directly visible. We
			// render the tile and all non-mobile entities in dark gray.
			if(isApplyVisSet)
			{
				this.buffer.setCell(x + this.xOfs, y + this.yOfs, tile.character, Color.darkGray, Color.black);
				if(this._entCountMap[ofs] > 0)
				{
					for(var i = 0; i < this._ents.length; ++i)
					{
						ent = this._ents[i];
						if(ent.isMobile ||
							ent.x != x ||
							ent.y != y)
							continue;
						this.buffer.setCell(ent.x + this.xOfs, ent.y + this.yOfs, ent.character, Color.darkGray, Color.black);
					}
				}
				this.target.setRawCell(x + this.xOfs, y + this.yOfs, this.buffer.getCell(x + this.xOfs, y + this.yOfs));
			}
			else
			{
				this.buffer.setCell(x + this.xOfs, y + this.yOfs, null, Color.darkGray, Color.black);
				this.target.setRawCell(x + this.xOfs, y + this.yOfs, this.buffer.getCell(x + this.xOfs, y + this.yOfs));
			}
		}
		else
		{
			this.buffer.setCell(x + this.xOfs, y + this.yOfs, " ", null, Color.black);
			this.target.setRawCell(x + this.xOfs, y + this.yOfs, this.buffer.getCell(x + this.xOfs, y + this.yOfs));
		}
	},
	/** Render the entire map.
	 */
	render: function()
	{
		if(!this.buffer ||
			!this.target)
			return;
		
		this.buffer.blitTo(this.target, this.xOfs, this.yOfs);
	},
	/** Draw an overlay on the map
	@param x X position.
	@param y Y position.
	@param c Character.
	@param fg Foreground color.
	@param bg Background color.
	*/
	renderOverlay: function(x, y, c, fg, bg)
	{
		if(!this.buffer ||
			!this.target ||
			x < 0 ||
			y < 0 ||
			x >= this.width ||
			y >= this.height)
			return;
		this.buffer.setCell(x + this.xOfs, y + this.yOfs, c, fg, bg);
		this.target.setRawCell(x + this.xOfs, y + this.yOfs, this.buffer.getCell(x + this.xOfs, y + this.yOfs));
	}
};
ron.registerCtor(Map, "Map");
